package com.mpb.prj.practice;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.expasy.jpl.bio.exceptions.JPLAAByteUndefinedException;
import org.expasy.jpl.bio.sequence.JPLAASequence;
import org.expasy.jpl.bio.sequence.JPLIAASequence;
import org.expasy.jpl.bio.sequence.io.fasta.JPLFastaEntry;
import org.expasy.jpl.bio.sequence.io.fasta.JPLFastaHeader;
import org.expasy.jpl.bio.sequence.io.fasta.JPLFastaHeaderFormat;
import org.expasy.jpl.bio.sequence.io.fasta.JPLFastaReader;
import org.expasy.jpl.bio.sequence.io.fasta.JPLFastaWriter;
import org.expasy.jpl.bio.sequence.rich.JPLPFFSequence;
import org.expasy.jpl.bio.sequence.tools.positions.cutter.JPLCleaver;
import org.expasy.jpl.experimental.ms.peaklist.JPLFragmentationSpectrum;
import org.expasy.jpl.insilico.exceptions.JPLPrecursorUnfragmentableException;
import org.expasy.jpl.insilico.ms.fragmentation.JPLFragmentationType;
import org.expasy.jpl.insilico.ms.fragmentation.fragmenter.JPLPeptideFragmenter;
import org.expasy.jpl.insilico.ms.peaklist.JPLTheoMSnPeakList;
import org.expasy.jpl.matching.algo.JPLSpectralMatcher;
import org.expasy.jpl.matching.exception.JPLSpectralMatcherException;

public class JPLFastaFileGenerator {

	private static final int SEQUENCE_REVERSE = 1;
	private static final int SEQUENCE_SHUFFLE = 2;
	private static final int PEPTIDE_SHUFFLE = 3;

	private String fileName;
	private JPLCleaver enzyme;

	public JPLFastaFileGenerator(String fileName, JPLCleaver enzyme) {
		this.fileName = fileName;
		this.enzyme = enzyme;
	}

	/*
	 * This function takes accessionCode as an input and returns protein
	 * sequence or null.
	 * 
	 * Input: String
	 * 
	 * Returns: String on successful or null on fail
	 */
	public String findProteinSequence(String accessionCode) {

		JPLFastaReader fastaScanner;
		String sequence = null;

		try {
			fastaScanner = new JPLFastaReader(fileName);
			JPLFastaHeaderFormat spHeaderFormat = new JPLFastaHeaderFormat(
					"sp", "^sp\\|.+$", '|');
			spHeaderFormat.setAccessionNumberField(1);
			JPLFastaHeaderFormat ipiHeaderFormat = new JPLFastaHeaderFormat(
					"ipi", "^IPI.+$", ' ');
			ipiHeaderFormat.setAccessionNumberField(0);

			fastaScanner.setHeaderFormat(spHeaderFormat);
			fastaScanner.addHeaderFormat(ipiHeaderFormat);

			while (fastaScanner.hasNext()) {
				JPLFastaEntry nextEntry = fastaScanner.next();
				if (nextEntry.getHeader().getAccessionNumber().equals(
						accessionCode)) {
					sequence = nextEntry.getSequence();
					return sequence;
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private JPLTheoMSnPeakList createTheoreticalSpectrum(String peptideSequence) throws JPLPrecursorUnfragmentableException, JPLAAByteUndefinedException {
		JPLTheoMSnPeakList peakList = null;

			JPLPFFSequence pffSequence = new JPLPFFSequence.Builder(peptideSequence).build();
			JPLPeptideFragmenter.setFragmentablePresursorMinLength(1);
			JPLPeptideFragmenter fragmenter = new JPLPeptideFragmenter(JPLFragmentationType.BY);
			fragmenter.setFragmentablePrecursor(pffSequence, 1);
		// set the charge
			if (fragmenter.getFragmentablePrecursor().isFragmentable()) {
			// of fragment
			// ion
				fragmenter.generateIonFragments();
			}
			peakList = fragmenter.getPeakList();		
	
		return peakList;
	}

	/*
	 * This function takes file name as an input and creates the fasta file with
	 * shuffled sequence
	 * 
	 * @param outFileName as a String
	 */
	public void createsShuffledFastaFile(String outFileName) {
		createFastaFile(outFileName, SEQUENCE_SHUFFLE);
	}

	/**
	 * This function takes file name as an input and creates the fasta file with
	 * reverse sequence
	 * 
	 * Input: String
	 */
	public void createReversedFastaFile(String outFileName) {
		createFastaFile(outFileName, SEQUENCE_REVERSE);
	}

	/**
	 * This function takes file name as an input and creates the fasta file with
	 * shuffled peptide sequence
	 * 
	 * Input: String
	 */
	public void createPeptideShuffledFastaFile(String outFileName) {
		createFastaFile(outFileName, PEPTIDE_SHUFFLE);
	}

	public void displayShuffledPeptideScores() {
		JPLFastaReader fastaScanner;
		String sequence = null;
		int row = 0;
		try {
			fastaScanner = new JPLFastaReader(fileName);

			JPLFastaHeaderFormat spHeaderFormat = new JPLFastaHeaderFormat(
					"sp", "^sp\\|.+$", '|');
			spHeaderFormat.setAccessionNumberField(1);
			JPLFastaHeaderFormat ipiHeaderFormat = new JPLFastaHeaderFormat(
					"ipi", "^IPI.+$", ' ');
			ipiHeaderFormat.setAccessionNumberField(0);
			fastaScanner.setHeaderFormat(spHeaderFormat);
			fastaScanner.addHeaderFormat(ipiHeaderFormat);
			while (fastaScanner.hasNext()) {
				JPLFastaEntry nextEntry = fastaScanner.next();
				sequence = nextEntry.getSequence();
				//System.out.println(row + " sequence: " + sequence);	
				calculateSequenceMatchingScores(sequence);
				row++;
			}
		} catch (IOException e) {

			e.printStackTrace();
		} catch (JPLPrecursorUnfragmentableException e) {
			System.out.println(sequence);
			e.printStackTrace();
		} catch (JPLAAByteUndefinedException e) {
			System.out.println(sequence);
			e.printStackTrace();
		} catch (JPLSpectralMatcherException e) {
			System.out.println(sequence);
			e.printStackTrace();
		}
	}

	/**
	 * This function is for calculating the scores for reshuffling the duplicate peptides
	 * and reshuffling the highest score of a peptide sequence
	 * @param sequence
	 * @throws JPLPrecursorUnfragmentableException
	 * @throws JPLAAByteUndefinedException
	 * @throws JPLSpectralMatcherException
	 */
	public void calculateSequenceMatchingScores(String sequence)
			throws JPLPrecursorUnfragmentableException,
			JPLAAByteUndefinedException, JPLSpectralMatcherException {
		int counter = 0;
		List<JPLIAASequence> listDecoy = getSequenceReversedDigests(sequence);	//change revrse DB
		List<JPLIAASequence> listTarget = getDigests(sequence);

		if(listTarget == null || listDecoy == null)
			return;
		
		//listDecoy = reshufflingDuplicates(listDecoy);
		double[] scores = new double[listTarget.size()];

		for (Iterator<JPLIAASequence> itr = listDecoy.iterator(); itr.hasNext();) {
			JPLIAASequence decoySeq = itr.next();
			JPLIAASequence targetSeq = listTarget.get(counter++);

			if (decoySeq.toAAString().length() < 6)
				continue;
			scores[counter-1] = calcuatePeptideMatchingScore(decoySeq,targetSeq);
		}
		int temp = 0;
		for (int i = 0; i < scores.length; i++) {
			if (scores[i] > scores[temp])
				temp = i;
		}		
		scores[temp] = calcuatePeptideMatchingScore( shufflePeptide(listDecoy.get(temp)),listTarget.get(temp));
		//System.out.println("score after reshuffle: " + scores[temp]);
	}
	
/**
 * This function is for calculate the scores for every peptide
 * @param decoySeq
 * @param sourceSeq
 * @return
 * @throws JPLPrecursorUnfragmentableException
 * @throws JPLAAByteUndefinedException
 * @throws JPLSpectralMatcherException
 */
	private double calcuatePeptideMatchingScore(JPLIAASequence decoySeq,JPLIAASequence sourceSeq) throws JPLPrecursorUnfragmentableException, JPLAAByteUndefinedException, JPLSpectralMatcherException {
		
		JPLTheoMSnPeakList theoreticalDecoy = createTheoreticalSpectrum(decoySeq
				.toAAString());
		JPLTheoMSnPeakList theoreticalSource = createTheoreticalSpectrum(sourceSeq
				.toAAString());

		double[] massesTarget = theoreticalSource.getMzs();
		double[] massesDecoy = theoreticalDecoy.getMzs();

		double[] intensities = new double[theoreticalDecoy.getNbPeak()];
		for (int i = 0; i < theoreticalDecoy.getNbPeak(); i++) {
			intensities[i] = 1;
		}
		JPLFragmentationSpectrum spectrumTarget = new JPLFragmentationSpectrum();
		spectrumTarget.setMzs(massesTarget);
		spectrumTarget.setIntensities(intensities);
		JPLFragmentationSpectrum spectrumDecoy = new JPLFragmentationSpectrum();
		if (massesDecoy != null) {
			spectrumDecoy.setMzs(massesDecoy);
			spectrumDecoy.setIntensities(intensities);
		}

		double fragmentMassTol = 0.1;
		JPLSpectralMatcher jplsm = new JPLSpectralMatcher(spectrumDecoy, spectrumTarget);		
		jplsm.setTolerance((int)fragmentMassTol);		
		jplsm.computeMatch();
		double nbMatching = jplsm.getScore();
		double nbPeaks = spectrumDecoy.getNbPeak();
		double score = nbMatching / nbPeaks;
		return score;

	}

	/**
	 * This function takes file name and actionType as an input, this function
	 * creates fasta file with shuffled peptide sequence
	 * 
	 * Input: String
	 * 
	 * Returns: String on successful or null on fail
	 */
	private void createFastaFile(String outFileName, int actionType) {

		JPLFastaReader fastaScanner;
		JPLFastaWriter writer;
		String sequence = null;
		String decoy = null;
		try {
			fastaScanner = new JPLFastaReader(fileName);
			writer = new JPLFastaWriter(outFileName);

			JPLFastaHeaderFormat spHeaderFormat = new JPLFastaHeaderFormat(
					"sp", "^sp\\|.+$", '|');
			spHeaderFormat.setAccessionNumberField(1);

			JPLFastaHeaderFormat ipiHeaderFormat = new JPLFastaHeaderFormat(
					"ipi", "^IPI.+$", ' ');
			ipiHeaderFormat.setAccessionNumberField(0);
			fastaScanner.setHeaderFormat(spHeaderFormat);
			fastaScanner.addHeaderFormat(ipiHeaderFormat);
			while (fastaScanner.hasNext()) {
				JPLFastaEntry nextEntry = fastaScanner.next();
				JPLFastaHeader nextHeader = nextEntry.getHeader();
				sequence = nextEntry.getSequence();
	
				if (actionType == SEQUENCE_SHUFFLE) {
					decoy = getShuffledString(sequence);
					}

				else if (actionType == PEPTIDE_SHUFFLE) {
					decoy = getShuffledPeptideSequence(sequence);
					
				} else if (actionType == SEQUENCE_REVERSE)
					decoy = getReversedString(sequence);
				else
					decoy = getReversedString(sequence);
				
				if(decoy == null || decoy.length() <=0 )
					continue;					
								
				JPLFastaHeader decoyHeader = new JPLFastaHeader("DECOY_" + nextHeader.getAccessionNumber());
				JPLFastaEntry entry = new JPLFastaEntry(decoyHeader, decoy);
				writer.addEntry(entry);
			}
			writer.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
/**
 * This function is for shuffling the duplicate peptides
 * @param shuffledPeptides
 * @return
 */
	@SuppressWarnings("unused")
	private List<JPLIAASequence> reshufflingDuplicates(List<JPLIAASequence> shuffledPeptides) {
		JPLIAASequence duplicateObject1;
		JPLIAASequence duplicateObjects2;
		for (int i = 0; i < shuffledPeptides.size(); i++) {
			duplicateObject1 = shuffledPeptides.get(i);
			if (duplicateObject1.toAAString().length() < 6)
				continue;

			for (int j = i + 1; j < shuffledPeptides.size(); j++) {
				duplicateObjects2 = shuffledPeptides.get(j);
				if (duplicateObject1.toAAString().equals(duplicateObjects2.toAAString())) {
					System.out.println("Duplicate found");
					System.out.println(duplicateObjects2.toAAString());
					duplicateObjects2 = shufflePeptide(duplicateObjects2);
					shuffledPeptides.remove(j);
					shuffledPeptides.add(j, duplicateObjects2);
				}
			}
		}
		return shuffledPeptides;
	}

	private JPLIAASequence shufflePeptide(JPLIAASequence peptide) {
		String tmp = peptide.toAAString();
///		System.out.println(" Before shuffle: " + tmp);
//		System.out.println(peptide.getRootSequence());
		String discard = tmp.substring(tmp.length() - 1);
		tmp = getShuffledString(tmp.substring(0, tmp.length() - 1)) + discard;
		peptide = new JPLAASequence.Builder(tmp).build();
//		System.out.println("After reshuffle: " + peptide.toAAString());
		return peptide;
	}

	/**
	 * This function takes sequence as an input and returns the peptides after
	 * digesting with enzyme.
	 * 
	 * @param string
	 * 
	 * @return Set of JPLIAASequence objects
	 * 		   null if sequence is ambiguous
	 */

	public List<JPLIAASequence> getDigests(String string) {
		JPLIAASequence aas = new JPLAASequence.Builder(string).allowAmbiguousAA().build();
		if(aas.isAmbiguous()){
			System.out.println("Ambiguous is true");
			return null;
		}
		enzyme.digest(aas);
		Set<JPLIAASequence> peptides =  enzyme.getUniqueDigests();
		//List<JPLIAASequence> peptides =  enzyme.getDigests();
		// convert from set to List
		List<JPLIAASequence> uniquePeptides = new ArrayList<JPLIAASequence>(peptides);
		return uniquePeptides;	
	}
	

	/**
	 * This function takes sequence as an input and returns a sequence
	 * 
	 * @param sequence
	 * @return
	 */
/*
	public String getpeptideSequence(String sequence) {
		List<JPLIAASequence> peptides = getDigests(sequence);
		StringBuffer decoy_protein = new StringBuffer();
		for (JPLIAASequence peptide : peptides) {
			decoy_protein.append(peptide.toAAString());
		}

		return decoy_protein.toString();
	}
*/
	
	/**
	 * This function takes sequence as an input and returns the peptides in Set
	 * after reversing the sequence in the peptides.
	 * 
	 * @param sequence
	 * 
	 * @return Set of JPLIAASequence objects
	 */
	public List<JPLIAASequence> getSequenceReversedDigests(String sequence) {

		List<JPLIAASequence> peptides = getDigests(sequence);
		if(peptides == null)
			return null;
		List<JPLIAASequence> reversePeptides = new ArrayList<JPLIAASequence>();
		//JPLIAASequence peptide = null;
		for (JPLIAASequence peptide : peptides) {
			String aminoAcidString = peptide.toAAString();
			StringBuffer strb = new StringBuffer();
			strb.append(getReversedString(aminoAcidString));
			peptide = new JPLAASequence.Builder(strb.toString())
					.allowAmbiguousAA().build();
			reversePeptides.add(peptide);
		}
		
		return reversePeptides;
	}

	/*
	 * This function takes a string and returns the reversed string.
	 * 
	 * Input: String
	 * 
	 * Returns: String
	 */
	private String getReversedString(String string) {
		return new StringBuffer(string).reverse().toString();
	}

	/*
	 * This function takes string as an input and returns shuffled string.
	 * 
	 * Input: String
	 * 
	 * Returns: String
	 */
	public String getShuffledString(String string) {
		ArrayList<Character> arrayList = stringToCharList(string);
		Collections.shuffle(arrayList);
		return charListToString(arrayList);
	}

	/**
	 * This function takes string as an input and returns ArrayList of
	 * characters.
	 * 
	 * @param sequence
	 *            as String
	 * @return ArrayList<Character>
	 */
	private ArrayList<Character> stringToCharList(String sequence) {
		ArrayList<Character> l = new ArrayList<Character>();
		for (int i = 0; i < sequence.length(); i++) {
			l.add(new Character(sequence.charAt(i)));	
		}
		return l;
	}

	/*
	 * This function takes ArrayList of characters as an input and returns
	 * String.
	 * 
	 * Input: String
	 * 
	 * Returns: ArrayList<Character>
	 */
	private String charListToString(ArrayList<Character> list) {
		StringBuffer str = new StringBuffer();
		for (int i = 0; i < list.size(); i++) {
			Character c = list.get(i);
			str.append(c.charValue());
		}
		return str.toString();
	}

	/**
	 * This function takes sequence string as an input and returns the peptides
	 * after shuffling the sequence.
	 * 
	 * @param sequence
	 * 
	 * @return Set
	 */
	public List<JPLIAASequence> getShuffledPeptides(String sequence) {

		List<JPLIAASequence> peptides = getDigests(sequence);
		if(peptides == null)
			return null;
		
		List<JPLIAASequence> shuffledPeptides = new ArrayList<JPLIAASequence>();
		JPLIAASequence peptide = null;

		for (int i = 0; i < peptides.size(); i++) {
			peptide = peptides.get(i);
			String aminoAcidString = peptide.toAAString();
			char lastchar = aminoAcidString
					.charAt(aminoAcidString.length() - 1);
			if (aminoAcidString.length() > 2) {
				CharSequence tmp = aminoAcidString.subSequence(0,
						aminoAcidString.length() - 1);
				tmp = getShuffledString(tmp.toString());
				StringBuffer strb = new StringBuffer();
				strb.append(tmp).append(lastchar);
				peptide = new JPLAASequence.Builder(strb.toString())
						.allowAmbiguousAA().build();

			}
			shuffledPeptides.add(i, peptide);
		}
		return shuffledPeptides;
	}

	/**
	 * This function takes sequence as an input and returns the shuffled
	 * peptides sequence
	 * 
	 * @param String
	 *            : sequence
	 * 
	 * @return String
	 */

	public String getShuffledPeptideSequence(String sequence) {
		List<JPLIAASequence> peptides = getShuffledPeptides(sequence);
		if(peptides == null)
			return null;
		
		StringBuffer shuffledseq = new StringBuffer();
		for (JPLIAASequence peptide : peptides) {
			shuffledseq.append(peptide.toAAString());
		}
		return shuffledseq.toString();
	}
}